
#include <stdio.h>
#include <time.h>
#include <BDate>
#include <BTime>
#include <BFolder>
#include "member_BFolder.h"
#include "member_BEntry.h"

using namespace BWE;

#define member					(*(member_BFolder*)_ptr)
#define member_allocate()		_ptr = new member_BFolder(this)
#define member_release()		delete (member_BFolder*)_ptr

#ifdef linux
BString BFolder::FullPath(const BString& path)
{
	char abpath[80];
	realpath(path.cstr(), abpath);
	return abpath;
}
bool BFolder::Create(const BString& path)
{
	int ret = mkdir(path.cstr(), 1);
	return ret == 0;
}
bool BFolder::Remove(const BString& path)
{
	int ret = rmdir(path.cstr());
	return ret == 0;
}
bool BFolder::Rename(const BString& path, const BString& newName)
{
	if (path.contain('/'))
	{
		BString newFileName = path.path();
		newFileName << '/' << newName;
		int ret = ::rename(path.cstr(), newFileName.cstr());
		return ret == 0;
	}
	int ret = ::rename(path.cstr(), newName.cstr());
	return ret == 0;
}
bool BFolder::Exist(const BString& path)
{
	Attrib attrib = BEntry::Check(path);
	return attrib & Attrib_Folder;
}
bool BFolder::Move(const BString& fileName, const BString& path)
{
	BString newFileName = path;
	newFileName << '/' << fileName.name();
	int ret = ::rename(fileName.cstr(), newFileName.cstr());
	return ret == 0;
}

void BFolder::setNotifying(bool notifying)
{
	if(member.nty == 0 && notifying)
	{
		member.nty = inotify_init();
		if(-1 == inotify_add_watch(member.nty, member.path.cstr(), IN_ALL_EVENTS))
		{
			perror("inotify_add_watch");
		}
	}
}
bool BFolder::notifying() const
{
	return member.nty;
}

bool BFolder::open()
{
	if(member.isOpened)
		return true;

	member.entries.clear();
	member.dir = opendir(member.path.cstr());
	if(!member.dir)
		return false;

	struct tm dtime;
	struct stat st;
	while(dirent* de = readdir(member.dir))
	{
		BString name = de->d_name;
		if(name == '.' || name == "..")
			continue;
		BEntry* entry = new BEntry();
		entry->setName(name);
		entry_member(entry)->path = member.path;
		entry_member(entry)->path << '/' << name;
		const BString& filePath = entry_member(entry)->path;
		if(::stat(entry_member(entry)->path.cstr(), &st) == 0)  
		{
			entry_member(entry)->size = st.st_size;
			localtime_r(&st.st_mtim.tv_sec, &dtime);
			entry_member(entry)->date.set(dtime.tm_year + 1900, dtime.tm_mon, dtime.tm_mday);
			entry_member(entry)->time.set(dtime.tm_hour, dtime.tm_min, dtime.tm_sec);	
		}
		Attrib attribs = Attrib_None;
		switch (de->d_type)
		{
			case DT_REG:		attribs = Attrib_File;		break;
			case DT_DIR:		attribs = Attrib_Folder;	break;
			case DT_BLK:		attribs = Attrib_File;		break;
			case DT_FIFO:		attribs = Attrib_Pipe;		break;
			case DT_LNK:		attribs = Attrib_Link;		break;
			case DT_SOCK:		attribs = Attrib_Socket;	break;
			default:			attribs = Attrib_None;		break;
		}
		entry_member(entry)->attrib =  attribs;
		member.entries.append(entry);
	}
	member.isOpened = true;
	return member.isOpened;
}

bool BFolder::close()
{
	if(member.isOpened)
	{
		int ret = closedir(member.dir);
		member.isOpened = false;
		member.entries.clear();
		return ret == 0;
	}
	return false;
}

#endif

#ifdef _WIN32

BString BFolder::FullPath(const BString& path)
{
	char buffer[256];
	DWORD len = 0;
	if (path.endWith(':'))
	{
		BString temp = path;
		temp += '\\';
		len = ::GetFullPathNameA(temp.cstr(), 256, buffer, 0);
	}
	else
	{
		len = ::GetFullPathNameA(path.cstr(), 256, buffer, 0);
	}
	for (DWORD i = 0; i < len; i++)
	{
		if (buffer[i] == '\\')
			buffer[i] = '/';
	}
	return buffer;
}

bool BFolder::Create(const BString& path)
{
	if (path.contain('/'))
	{
		BString temp;
		int end = path.find('/');
		while (end < path.size())
		{
			path.substr(temp, 0, end);
			if (false == BFolder::Exist(temp))
			{
				if (FALSE == CreateDirectoryA(temp.cstr(), 0))
				{
					return false;
				}
			}
			end = path.find('/', end + 1);
		}
	}
	BOOL ret = CreateDirectoryA(path.cstr(), 0);
	return ret == TRUE;
}
bool BFolder::Remove(const BString& path)
{
	if (RemoveDirectoryA(path.cstr()) == TRUE)
		return true;
	SHFILEOPSTRUCT fop;
	fop.pFrom = path.cstr();
	fop.fFlags = FOF_NOCONFIRMATION;
	fop.hNameMappings = NULL;
	fop.hwnd = NULL;
	fop.lpszProgressTitle = NULL;
	fop.pTo = NULL;
	fop.wFunc = FO_DELETE;
	int iret = SHFileOperationA(&fop);
	return iret == 0;
}
bool BFolder::Rename(const BString& path, const BString& newPath)
{
	if (path.contain('/'))
	{
		BString temp = path.path();
		temp << '/' << newPath;
		BOOL ret = MoveFileA(path.cstr(), temp.cstr());
		return ret == TRUE;
	}
	BOOL ret = MoveFileA(path.cstr(), newPath.cstr());
	return ret == TRUE;
}
bool BFolder::Move(const BString& path, const BString& name)
{
	BString filePath = name;
	if (name.endWith('/'))
		filePath << '/';
	filePath << path.name();
	BOOL ret = MoveFileA(path.cstr(), filePath.cstr());
	return ret == TRUE;
}
bool BFolder::Exist(const BString& path)
{
	Attrib attrib = BEntry::Check(path);
	return attrib & Attrib_Folder;
}

bool BFolder::open()
{
	if (member.isOpened)
		return true;
	if (member.path.empty())
		return false;
	member.entries.clear();

	BString pathStr = member.path;
	if (member.path.last() != '/')
		pathStr << '/';
	pathStr << '*';

	WIN32_FIND_DATA EntryData;
	HANDLE hSearch = FindFirstFile(pathStr.cstr(), &EntryData);
	if (hSearch == INVALID_HANDLE_VALUE)
		return false;

	while (FindNextFile(hSearch, &EntryData))
	{
		BString name = EntryData.cFileName;
		if (name == '.' || name == "..")
			continue;
		Attrib attrib = ConvertEntryAttrib(EntryData.dwFileAttributes);
		if (attrib == Attrib_None)
			continue;
		if (member.attribFilter & attrib)
		{
			BEntry* entry = new BEntry();
			entry->setName(name);
			entry_member(entry)->attrib = attrib;
			entry_member(entry)->path = member.path;
			entry_member(entry)->path << '/' << name;
			SYSTEMTIME st;
			FileTimeToSystemTime(&EntryData.ftLastWriteTime, &st);
			st.wHour = (st.wHour + 8) % 24;
			BDate date(st.wYear, st.wMonth, st.wDay);
			BTime time(st.wHour, st.wMinute, st.wSecond);
			entry->setDate(date);
			entry->setTime(time);
			entry->setSize(EntryData.nFileSizeLow);
			member.entries.append(entry);
		}
	}
	FindClose(hSearch);
	member.isOpened = true;
	return true;
}
bool BFolder::close()
{
	if(member.isOpened)
	{
		member.isOpened = false;
		member.entries.clear();
		return true;
	}
	return false;
}

#endif

BFolder::BFolder()
{
	member_allocate();
}
BFolder::BFolder(const BString& folderPath, Attrib attribFilter)
{
	member_allocate();
	setPath(folderPath);
	setAttribFilter(attribFilter);
}
BFolder::~BFolder()
{
	member_release();
}

bool BFolder::isOpened() const
{
	return member.isOpened;
}

void BFolder::setPath(const BString& path)
{
	if (member.path != path)
	{
		member.path = path;
		if (member.isOpened)
		{
			close();
		}
	}
}
const BString& BFolder::path() const
{
	return member.path;
}

void BFolder::setAttribFilter(Attrib attribFilter)
{
	if (member.attribFilter != attribFilter)
	{
		member.attribFilter = attribFilter;
	}
}
Attrib BFolder::attribFilter() const
{
	return member.attribFilter;
}

void BFolder::setNameFilter(const BString& nameFilter)
{
	if (member.nameFilter != nameFilter)
	{
		member.nameFilter = nameFilter;
	}
}
const BString& BFolder::nameFilter() const
{
	return member.nameFilter;
}

bool BFolder::contain(const BString& entryName)
{
	for(int i = 0; i < member.entries.size(); i++)
		if(member.entries[i]->name() == entryName)
			return true;
	return false;
}

int BFolder::entryCount() const
{
	return member.entries.size();
}

BEntry* BFolder::entry(int index)
{
	return member.entries(index).ptr();
}
const BEntry* BFolder::entry(int index) const
{
	return member.entries(index).ptr();
}

BEntry* BFolder::entry(const BString& name)
{
	for (int i = 0; i < member.entries.size(); i++)
	{
		BEntry* entry = member.entries[i];
		if (entry->name() == name)
			return entry;
	}
	return 0;
}
const BEntry* BFolder::entry(const BString& name) const
{
	for (int i = 0; i < member.entries.size(); i++)
	{
		const BEntry* entry = member.entries[i];
		if (entry->name() == name)
			return entry;
	}
	return 0;
}

BEntry* BFolder::find(const BString& desc)
{
	for (int i = 0; i < member.entries.size(); i++)
	{
		BEntry* entry = member.entries[i];
		if (entry->name().contain(desc))
			return entry;
	}
	return 0;
}
const BEntry* BFolder::find(const BString& desc) const
{
	for (int i = 0; i < member.entries.size(); i++)
	{
		const BEntry* entry = member.entries[i];
		if (entry->name().contain(desc))
			return entry;
	}
	return 0;
}
